﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RecordReaderDefault.cs" company="Rolosoft Ltd">
//   © Rolosoft Ltd
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// Copyright 2013 Rolosoft Ltd
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace Rsft.Net.Dns
{
    #region Usings

    using System.Collections.Generic;
    using System.Text;

    using Rsft.Net.Dns.Entities;
    using Rsft.Net.Dns.Entities.Records;
    using Rsft.Net.Dns.Entities.Records.NotUsed;
    using Rsft.Net.Dns.Entities.Records.Obsolete;
    using Rsft.Net.Dns.Extensions;
    using Rsft.Net.Dns.Interfaces;

    #endregion

    /// <summary>
    ///     The default record reader.
    /// </summary>
    internal sealed class RecordReaderDefault : IRecordReader
    {
        #region Fields

        /// <summary>
        ///     The data
        /// </summary>
        private readonly byte[] data;

        /// <summary>
        ///     The position.
        /// </summary>
        private int position;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RecordReaderDefault"/> class.
        /// </summary>
        /// <param name="data">
        /// The data.
        /// </param>
        /// <param name="position">
        /// The position.
        /// </param>
        public RecordReaderDefault(byte[] data, int position = 0)
        {
            this.data = data;
            this.position = position;
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets or sets the position.
        /// </summary>
        public int Position
        {
            get
            {
                return this.position;
            }

            set
            {
                this.position = value;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        ///     The read byte.
        /// </summary>
        /// <returns>
        ///     The <see cref="byte" />.
        /// </returns>
        public byte ReadByte()
        {
            if (this.position >= this.data.Length)
            {
                return 0;
            }

            return this.data[this.position++];
        }

        /// <summary>
        /// The read bytes.
        /// </summary>
        /// <param name="length">
        /// The length.
        /// </param>
        /// <returns>
        /// The <see cref="byte[]"/>.
        /// </returns>
        public byte[] ReadBytes(int length)
        {
            var list = new List<byte>();
            for (var intI = 0; intI < length; intI++)
            {
                list.Add(this.ReadByte());
            }

            return list.ToArray();
        }

        /// <summary>
        ///     The read domain name.
        /// </summary>
        /// <returns>
        ///     The <see cref="string" />.
        /// </returns>
        public string ReadDomainName()
        {
            var sb = new StringBuilder();
            int length;

            while ((length = this.ReadByte()) != 0)
            {
                if ((length & 0xc0) == 0xc0)
                {
                    // derive the existing domain name, copy this pointer
                    var newRecordReader = new RecordReaderDefault(this.data, (length & 0x3f) << 8 | this.ReadByte());

                    sb.Append(newRecordReader.ReadDomainName());
                    return sb.ToString();
                }

                while (length > 0)
                {
                    sb.Append(this.ReadChar());
                    length--;
                }

                sb.Append('.');
            }

            var rtn = sb.Length == 0 ? "." : sb.ToString();

            var removeTrailingDot = RecordFormatting.RemoveTrailingDot(rtn);

            return removeTrailingDot;
        }

        /// <summary>
        /// The read record.
        /// </summary>
        /// <param name="recordType">
        /// The DNS record type.
        /// </param>
        /// <returns>
        /// The <see cref="Record"/>.
        /// </returns>
        public Record ReadRecord(RecordType recordType)
        {
            switch (recordType)
            {
                case RecordType.A:
                    return new A(this);
                case RecordType.NS:
                    return new NS(this);
                case RecordType.MD:
                    return new MD(this);
                case RecordType.MF:
                    return new MF(this);
                case RecordType.CNAME:
                    return new CNAME(this);
                case RecordType.SOA:
                    return new SOA(this);
                case RecordType.MB:
                    return new MB(this);
                case RecordType.MG:
                    return new MG(this);
                case RecordType.MR:
                    return new MR(this);
                case RecordType.NULL:
                    return new NULL(this);
                case RecordType.WKS:
                    return new WKS(this);
                case RecordType.PTR:
                    return new PTR(this);
                case RecordType.HINFO:
                    return new HINFO(this);
                case RecordType.MINFO:
                    return new MINFO(this);
                case RecordType.MX:
                    return new MX(this);
                case RecordType.TXT:
                    return new TXT(this);
                case RecordType.RP:
                    return new RP(this);
                case RecordType.AFSDB:
                    return new AFSDB(this);
                case RecordType.X25:
                    return new X25(this);
                case RecordType.ISDN:
                    return new ISDN(this);
                case RecordType.RT:
                    return new RT(this);
                case RecordType.NSAP:
                    return new NSAP(this);
                case RecordType.NSAPPTR:
                    return new NSAPPTR(this);
                case RecordType.SIG:
                    return new SIG(this);
                case RecordType.KEY:
                    return new KEY(this);
                case RecordType.PX:
                    return new PX(this);
                case RecordType.GPOS:
                    return new GPOS(this);
                case RecordType.AAAA:
                    return new AAAA(this);
                case RecordType.LOC:
                    return new LOC(this);
                case RecordType.NXT:
                    return new NXT(this);
                case RecordType.EID:
                    return new EID(this);
                case RecordType.NIMLOC:
                    return new NIMLOC(this);
                case RecordType.SRV:
                    return new SRV(this);
                case RecordType.ATMA:
                    return new ATMA(this);
                case RecordType.NAPTR:
                    return new NAPTR(this);
                case RecordType.KX:
                    return new KX(this);
                case RecordType.CERT:
                    return new CERT(this);
                case RecordType.A6:
                    return new A6(this);
                case RecordType.DNAME:
                    return new DNAME(this);
                case RecordType.SINK:
                    return new SINK(this);
                case RecordType.OPT:
                    return new OPT(this);
                case RecordType.APL:
                    return new APL(this);
                case RecordType.DS:
                    return new DS(this);
                case RecordType.SSHFP:
                    return new SSHFP(this);
                case RecordType.IPSECKEY:
                    return new IPSECKEY(this);
                case RecordType.RRSIG:
                    return new RRSIG(this);
                case RecordType.NSEC:
                    return new NSEC(this);
                case RecordType.DNSKEY:
                    return new DNSKEY(this);
                case RecordType.DHCID:
                    return new DHCID(this);
                case RecordType.NSEC3:
                    return new NSEC3(this);
                case RecordType.NSEC3PARAM:
                    return new NSEC3PARAM(this);
                case RecordType.HIP:
                    return new HIP(this);
                case RecordType.SPF:
                    return new SPF(this);
                case RecordType.UINFO:
                    return new UINFO(this);
                case RecordType.UID:
                    return new UID(this);
                case RecordType.GID:
                    return new GID(this);
                case RecordType.UNSPEC:
                    return new UNSPEC(this);
                case RecordType.TKEY:
                    return new TKEY(this);
                case RecordType.TSIG:
                    return new TSIG(this);
                default:
                    return new Unknown(this);
            }
        }

        /// <summary>
        ///     The read string.
        /// </summary>
        /// <returns>
        ///     The <see cref="string" />.
        /// </returns>
        public string ReadString()
        {
            short length = this.ReadByte();

            var rtn = new StringBuilder();
            for (var intI = 0; intI < length; intI++)
            {
                rtn.Append(this.ReadChar());
            }

            return rtn.ToString();
        }

        /// <summary>
        ///     The read unsigned integer 16.
        /// </summary>
        /// <returns>
        ///     The <see cref="ushort" />.
        /// </returns>
        public ushort ReadUInt16()
        {
            return (ushort)(this.ReadByte() << 8 | this.ReadByte());
        }

        /// <summary>
        /// The read unsigned integer 16.
        /// </summary>
        /// <param name="offset">
        /// The offset.
        /// </param>
        /// <returns>
        /// The <see cref="ushort"/>.
        /// </returns>
        public ushort ReadUInt16(int offset)
        {
            this.position += offset;
            return this.ReadUInt16();
        }

        /// <summary>
        ///     The read unsigned integer 32.
        /// </summary>
        /// <returns>
        ///     The <see cref="uint" />.
        /// </returns>
        public uint ReadUInt32()
        {
            return (uint)(this.ReadUInt16() << 16 | this.ReadUInt16());
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Reads the char.
        /// </summary>
        /// <returns>
        ///     The read char.
        /// </returns>
        private char ReadChar()
        {
            return (char)this.ReadByte();
        }

        #endregion
    }
}